masqmail

view src/accept.c @ 222:8cddc65765bd

added support for STARTTLS wrappers added the route config option `instant_helo' which causes masqmail, as SMTP client, not to wait for the server's 220 greeting. Instead if says EHLO right at once. You'll need this for STARTTLS wrappers that usually eat the greeting line.
author meillo@marmaro.de
date Fri, 23 Jul 2010 10:57:53 +0200
parents cd59a5b4d3dd
children 996b53a50f55
line source
1 /* MasqMail
2 Copyright (C) 1999-2001 Oliver Kurth
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
19 #include "masqmail.h"
20 #include "readsock.h"
22 gchar *prot_names[] = {
23 "local",
24 "bsmtp",
25 "smtp",
26 "esmtp",
27 "(unknown)" /* should not happen, but better than crashing. */
28 };
30 static gchar*
31 string_base62(gchar * res, guint value, gchar len)
32 {
33 static gchar base62_chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
34 gchar *p = res + len;
35 *p = '\0';
36 while (p > res) {
37 *(--p) = base62_chars[value % 62];
38 value /= 62;
39 }
40 return res;
41 }
43 static gint
44 _g_list_addr_isequal(gconstpointer a, gconstpointer b)
45 {
46 address *addr1 = (address *) a;
47 address *addr2 = (address *) b;
48 int ret;
50 if ((ret = strcasecmp(addr1->domain, addr2->domain)) == 0)
51 return strcmp(addr1->local_part, addr2->local_part);
52 else
53 return ret;
54 }
56 /* accept message from anywhere.
57 A locally originating message is indicated by msg->recieved_host == NULL
59 The -t option:
60 The ACC_RCPT_FROM_HEAD flag adds the recipients found in To/Cc/Bcc
61 headers to the recipients list.
62 The ACC_DEL_RCPTS flag makes only sense if the ACC_RCPT_FROM_HEAD
63 flag is given too. With ACC_DEL_RCPTS the recipients given on the
64 command line are removed from the ones given in headers.
65 */
67 accept_error
68 accept_message_stream(FILE * in, message * msg, guint flags)
69 {
70 gchar *line, *line1;
71 int line_size = MAX_DATALINE;
72 gboolean in_headers = TRUE;
73 header *hdr = NULL;
74 gint line_cnt = 0, data_size = 0;
76 line = g_malloc(line_size);
77 line[0] = '\0';
79 while (TRUE) {
80 int len = read_sockline1(in, &line, &line_size, 5 * 60, READSOCKL_CVT_CRLF);
82 line1 = line;
84 if ((line[0] == '.') && (!(flags & ACC_DOT_IGNORE))) {
85 if (line[1] == '\n') {
86 g_free(line);
87 break;
88 }
89 line1++;
90 }
92 if (len <= 0) {
93 if ((len == -1) && (flags & (ACC_DOT_IGNORE | ACC_NODOT_RELAX))) {
94 /* we got an EOF, and the last line was not terminated by a CR */
95 gint len1 = strlen(line1);
96 if (len1 > 0) { /* == 0 is 'normal' (EOF after a CR) */
97 if (line1[len1 - 1] != '\n') { /* some mail clients allow unterminated lines */
98 line1[len1] = '\n';
99 line1[len1 + 1] = '\0';
100 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
101 data_size += strlen(line1);
102 line_cnt++;
103 }
104 }
105 break;
106 } else {
107 g_free(line);
108 if (len == -1) {
109 return AERR_EOF;
110 } else if (len == -2) {
111 /* should not happen any more */
112 return AERR_OVERFLOW;
113 } else if (len == -3) {
114 return AERR_TIMEOUT;
115 } else {
116 /* does not happen */
117 DEBUG(5) debugf("read_sockline returned %d\n", len);
118 return AERR_UNKNOWN;
119 }
120 }
121 } else {
122 if (in_headers) {
124 /* some pop servers send the 'From ' line, skip it: */
125 if (msg->hdr_list == NULL)
126 if (strncmp(line1, "From ", 5) == 0)
127 continue;
129 if (line1[0] == ' ' || line1[0] == '\t') {
130 /* continuation of 'folded' header: */
131 if (hdr) {
132 hdr->header = g_strconcat(hdr->header, line1, NULL);
133 }
135 } else if (line1[0] == '\n') {
136 /* an empty line marks end of headers */
137 in_headers = FALSE;
138 } else {
139 /* in all other cases we expect another header */
140 if ((hdr = get_header(line1)))
141 msg->hdr_list = g_list_append(msg->hdr_list, hdr);
142 else {
143 /* if get_header() returns NULL, no header was recognized,
144 so this seems to be the first data line of a broken mailer
145 which does not send an empty line after the headers */
146 in_headers = FALSE;
147 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
148 }
149 }
150 } else {
151 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
152 data_size += strlen(line1);
153 line_cnt++;
154 }
155 }
156 if (conf.max_msg_size && (data_size > conf.max_msg_size)) {
157 DEBUG(4) debugf("accept_message_stream(): "
158 "received %d bytes (conf.max_msg_size=%d)\n",
159 data_size, conf.max_msg_size);
160 return AERR_SIZE;
161 }
163 }
165 if (msg->data_list != NULL)
166 msg->data_list = g_list_reverse(msg->data_list);
167 else
168 /* make sure data list is not NULL: */
169 msg->data_list = g_list_append(NULL, g_strdup(""));
171 DEBUG(4) debugf("received %d lines of data (%d bytes)\n", line_cnt, data_size);
172 /* we get here after we succesfully received the mail data */
174 msg->data_size = data_size;
175 msg->received_time = time(NULL);
177 return AERR_OK;
178 }
180 accept_error
181 accept_message_prepare(message * msg, guint flags)
182 {
183 struct passwd *passwd = NULL;
184 GList *non_rcpt_list = NULL;
185 time_t rec_time = time(NULL);
187 DEBUG(5) debugf("accept_message_prepare()\n");
189 /* create unique message id */
190 msg->uid = g_malloc(14);
192 string_base62(msg->uid, rec_time, 6);
193 msg->uid[6] = '-';
194 string_base62(&(msg->uid[7]), getpid(), 3);
195 msg->uid[10] = '-';
196 string_base62(&(msg->uid[11]), msg->transfer_id, 2);
197 msg->uid[13] = 0;
199 /* if local, get password entry */
200 if (msg->received_host == NULL) {
201 passwd = g_memdup(getpwuid(geteuid()), sizeof(struct passwd));
202 msg->ident = g_strdup(passwd->pw_name);
203 }
205 /* set return path if local */
206 if (msg->return_path == NULL && msg->received_host == NULL) {
207 gchar *path = g_strdup_printf("<%s@%s>", passwd->pw_name, conf.host_name);
208 DEBUG(3) debugf("setting return_path for local accept: %s\n", path);
209 msg->return_path = create_address(path, TRUE);
210 g_free(path);
211 }
213 /* -t option (see comment above) */
214 if (flags & ACC_DEL_RCPTS) {
215 non_rcpt_list = msg->rcpt_list;
216 msg->rcpt_list = NULL;
217 }
219 /* scan headers */
220 {
221 gboolean has_id = FALSE;
222 gboolean has_date = FALSE;
223 gboolean has_sender = FALSE;
224 gboolean has_from = FALSE;
225 gboolean has_to_or_cc = FALSE;
226 GList *hdr_node, *hdr_node_next;
227 header *hdr;
229 for (hdr_node = g_list_first(msg->hdr_list);
230 hdr_node != NULL; hdr_node = hdr_node_next) {
231 hdr_node_next = g_list_next(hdr_node);
232 hdr = ((header *) (hdr_node->data));
233 DEBUG(5) debugf("scanning headers: %s", hdr->header);
234 switch (hdr->id) {
235 case HEAD_MESSAGE_ID:
236 has_id = TRUE;
237 break;
238 case HEAD_DATE:
239 has_date = TRUE;
240 break;
241 case HEAD_FROM:
242 has_from = TRUE;
243 break;
244 case HEAD_SENDER:
245 has_sender = TRUE;
246 break;
247 case HEAD_TO:
248 case HEAD_CC:
249 has_to_or_cc = TRUE;
250 /* fall through */
251 case HEAD_BCC:
252 if (flags & ACC_RCPT_FROM_HEAD) {
253 /* -t option (see comment above) */
254 DEBUG(5) debugf("hdr->value = %s\n", hdr->value);
255 if (hdr->value) {
256 msg->rcpt_list = addr_list_append_rfc822(msg->rcpt_list, hdr->value, conf.host_name);
257 }
258 }
259 if (hdr->id == HEAD_BCC) {
260 DEBUG(3) debugf("removing 'Bcc' header\n");
261 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
262 g_list_free_1(hdr_node);
263 destroy_header(hdr);
264 }
265 break;
266 case HEAD_ENVELOPE_TO:
267 if (flags & ACC_SAVE_ENVELOPE_TO) {
268 DEBUG(3) debugf("creating 'X-Orig-Envelope-To' header\n");
269 msg->hdr_list = g_list_prepend(msg->hdr_list, create_header(HEAD_UNKNOWN,
270 "X-Orig-Envelope-to: %s", hdr->value));
271 }
272 DEBUG(3) debugf("removing 'Envelope-To' header\n");
273 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
274 g_list_free_1(hdr_node);
275 destroy_header(hdr);
276 break;
277 case HEAD_RETURN_PATH:
278 if (flags & ACC_MAIL_FROM_HEAD) {
279 /* usually POP3 accept */
280 msg->return_path = create_address_qualified(hdr->value, TRUE, msg->received_host);
281 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
282 }
283 DEBUG(3) debugf("removing 'Return-Path' header\n");
284 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
285 g_list_free_1(hdr_node);
286 destroy_header(hdr);
287 break;
288 default:
289 break; /* make compiler happy */
290 }
291 }
293 if (msg->return_path == NULL) {
294 /* this can happen for pop3 accept only and if no Return-path: header was given */
295 GList *hdr_list;
296 header *hdr;
298 DEBUG(3) debugf("return_path == NULL\n");
300 hdr_list = find_header(msg->hdr_list, HEAD_SENDER, NULL);
301 if (!hdr_list)
302 hdr_list = find_header(msg->hdr_list, HEAD_FROM, NULL);
303 if (hdr_list) {
304 gchar *addr;
305 hdr = (header *) (g_list_first(hdr_list)->data);
307 DEBUG(5) debugf("hdr->value = '%s'\n", hdr->value);
309 addr = g_strdup(hdr->value);
310 g_strchomp(addr);
312 if ((msg->return_path = create_address_qualified(addr, FALSE, msg->received_host)) != NULL) {
313 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
314 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_UNKNOWN,
315 "X-Warning: return path set from %s address\n",
316 hdr->id == HEAD_SENDER ? "Sender:" : "From:"));
317 }
318 g_free(addr);
319 }
320 if (msg->return_path == NULL) { /* no Sender: or From: or create_address_qualified failed */
321 msg->return_path = create_address_qualified("postmaster", TRUE, conf.host_name);
322 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
323 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_UNKNOWN,
324 "X-Warning: real return path is unknown\n"));
325 }
326 }
328 if (flags & ACC_DEL_RCPTS) {
329 /* remove the recipients given on the command line from the ones given in headers
330 -t option (see comment above) */
331 GList *rcpt_node;
332 foreach(non_rcpt_list, rcpt_node) {
333 address *rcpt = (address *) (rcpt_node->data);
334 GList *node;
335 if ((node = g_list_find_custom(msg->rcpt_list, rcpt, _g_list_addr_isequal))) {
336 DEBUG(3) debugf("removing rcpt address %s\n", addr_string(node->data));
337 msg->rcpt_list = g_list_remove_link(msg->rcpt_list, node);
338 destroy_address((address *) (node->data));
339 g_list_free_1(node);
340 }
341 }
342 }
344 /* here we should have our recipients, fail if not: */
345 if (msg->rcpt_list == NULL) {
346 logwrite(LOG_WARNING, "no recipients found in message\n");
347 return AERR_NORCPT;
348 }
350 if (!(has_sender || has_from)) {
351 DEBUG(3) debugf("adding 'From' header\n");
352 msg->hdr_list = g_list_append(msg->hdr_list,
353 msg->full_sender_name
354 ?
355 create_header(HEAD_FROM, "From: \"%s\" <%s@%s>\n", msg->full_sender_name,
356 msg->return_path->local_part, msg->return_path->domain)
357 :
358 create_header(HEAD_FROM, "From: <%s@%s>\n",
359 msg->return_path->local_part, msg->return_path->domain)
360 );
361 }
362 if (!has_to_or_cc) {
363 DEBUG(3) debugf("no To: or Cc: header, hence adding `To: undisclosed recipients:;'\n");
364 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_TO, "To: undisclosed-recipients:;\n"));
365 }
366 if (!has_date) {
367 DEBUG(3) debugf("adding 'Date:' header\n");
368 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_DATE, "Date: %s\n", rec_timestamp()));
369 }
370 if (!has_id) {
371 DEBUG(3) debugf("adding 'Message-ID:' header\n");
372 msg->hdr_list = g_list_append(msg->hdr_list,
373 create_header(HEAD_MESSAGE_ID, "Message-ID: <%s@%s>\n", msg->uid, conf.host_name));
374 }
375 }
377 /* Received header: */
378 /* At this point because we have to know the rcpts for the 'for' part */
379 gchar *for_string = NULL;
380 header *hdr = NULL;
382 DEBUG(3) debugf("adding 'Received:' header\n");
384 if (g_list_length(msg->rcpt_list) == 1) {
385 address *addr = (address *) (g_list_first(msg->rcpt_list)->data);
386 for_string = g_strdup_printf(" for %s", addr_string(addr));
387 }
389 if (msg->received_host == NULL) {
390 /* received locally */
391 hdr = create_header(HEAD_RECEIVED, "Received: from %s by %s with %s (%s %s) id %s%s; %s\n",
392 passwd->pw_name, conf.host_name, prot_names[msg->received_prot],
393 PACKAGE, VERSION, msg->uid, for_string ? for_string : "", rec_timestamp());
394 } else {
395 /* received from remote */
396 #ifdef ENABLE_IDENT
397 DEBUG(5) debugf("adding 'Received:' header (5)\n");
398 hdr = create_header(HEAD_RECEIVED, "Received: from %s (ident=%s) by %s with %s (%s %s) id %s%s; %s\n",
399 msg->received_host, msg->ident ? msg->ident : "unknown", conf.host_name,
400 prot_names[msg->received_prot], PACKAGE, VERSION, msg->uid, for_string ? for_string : "",
401 rec_timestamp());
402 #else
403 hdr = create_header(HEAD_RECEIVED, "Received: from %s by %s with %s (%s %s) id %s%s; %s\n",
404 msg->received_host, conf.host_name, prot_names[msg->received_prot],
405 PACKAGE, VERSION, msg->uid, for_string ? for_string : "", rec_timestamp());
406 #endif
407 }
408 header_fold(hdr);
409 msg->hdr_list = g_list_prepend(msg->hdr_list, hdr);
411 if (for_string)
412 g_free(for_string);
414 /* write message to spool: */
415 /* accept is no longer responsible for this
416 if (!spool_write(msg, TRUE))
417 return AERR_NOSPOOL;
418 */
419 return AERR_OK;
420 }
422 accept_error
423 accept_message(FILE * in, message * msg, guint flags)
424 {
425 accept_error err;
427 err = accept_message_stream(in, msg, flags);
428 if (err == AERR_OK)
429 err = accept_message_prepare(msg, flags);
431 return err;
432 }