masqmail-0.2

view src/accept.c @ 171:349518b940db

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 Thu, 22 Jul 2010 23:30:05 +0200
parents 5ec5e6637049
children 087e99c7702a
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 "pop3",
28 "apop",
29 "(unknown)" /* should not happen, but better than crashing. */
30 };
32 static gchar*
33 string_base62(gchar * res, guint value, gchar len)
34 {
35 static gchar base62_chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
36 gchar *p = res + len;
37 *p = '\0';
38 while (p > res) {
39 *(--p) = base62_chars[value % 62];
40 value /= 62;
41 }
42 return res;
43 }
45 static gint
46 _g_list_addr_isequal(gconstpointer a, gconstpointer b)
47 {
48 address *addr1 = (address *) a;
49 address *addr2 = (address *) b;
50 int ret;
52 if ((ret = strcasecmp(addr1->domain, addr2->domain)) == 0)
53 return strcmp(addr1->local_part, addr2->local_part);
54 else
55 return ret;
56 }
58 /* accept message from anywhere.
59 A locally originating message is indicated by msg->recieved_host == NULL
61 The -t option:
62 The ACC_RCPT_FROM_HEAD flag adds the recipients found in To/Cc/Bcc
63 headers to the recipients list.
64 The ACC_DEL_RCPTS flag makes only sense if the ACC_RCPT_FROM_HEAD
65 flag is given too. With ACC_DEL_RCPTS the recipients given on the
66 command line are removed from the ones given in headers.
67 */
69 accept_error
70 accept_message_stream(FILE * in, message * msg, guint flags)
71 {
72 gchar *line, *line1;
73 int line_size = MAX_DATALINE;
74 gboolean in_headers = TRUE;
75 header *hdr = NULL;
76 gint line_cnt = 0, data_size = 0;
78 line = g_malloc(line_size);
79 line[0] = '\0';
81 while (TRUE) {
82 int len = read_sockline1(in, &line, &line_size, 5 * 60, READSOCKL_CVT_CRLF);
84 line1 = line;
86 if ((line[0] == '.') && (!(flags & ACC_DOT_IGNORE))) {
87 if (line[1] == '\n') {
88 g_free(line);
89 break;
90 }
91 line1++;
92 }
94 if (len <= 0) {
95 if ((len == -1) && (flags & (ACC_DOT_IGNORE | ACC_NODOT_RELAX))) {
96 /* we got an EOF, and the last line was not terminated by a CR */
97 gint len1 = strlen(line1);
98 if (len1 > 0) { /* == 0 is 'normal' (EOF after a CR) */
99 if (line1[len1 - 1] != '\n') { /* some mail clients allow unterminated lines */
100 line1[len1] = '\n';
101 line1[len1 + 1] = '\0';
102 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
103 data_size += strlen(line1);
104 line_cnt++;
105 }
106 }
107 break;
108 } else {
109 g_free(line);
110 if (len == -1) {
111 return AERR_EOF;
112 } else if (len == -2) {
113 /* should not happen any more */
114 return AERR_OVERFLOW;
115 } else if (len == -3) {
116 return AERR_TIMEOUT;
117 } else {
118 /* does not happen */
119 DEBUG(5) debugf("read_sockline returned %d\n", len);
120 return AERR_UNKNOWN;
121 }
122 }
123 } else {
124 if (in_headers) {
126 /* some pop servers send the 'From ' line, skip it: */
127 if (msg->hdr_list == NULL)
128 if (strncmp(line1, "From ", 5) == 0)
129 continue;
131 if (line1[0] == ' ' || line1[0] == '\t') {
132 /* continuation of 'folded' header: */
133 if (hdr) {
134 hdr->header = g_strconcat(hdr->header, line1, NULL);
135 }
137 } else if (line1[0] == '\n') {
138 /* an empty line marks end of headers */
139 in_headers = FALSE;
140 } else {
141 /* in all other cases we expect another header */
142 if ((hdr = get_header(line1)))
143 msg->hdr_list = g_list_append(msg->hdr_list, hdr);
144 else {
145 /* if get_header() returns NULL, no header was recognized,
146 so this seems to be the first data line of a broken mailer
147 which does not send an empty line after the headers */
148 in_headers = FALSE;
149 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
150 }
151 }
152 } else {
153 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
154 data_size += strlen(line1);
155 line_cnt++;
156 }
157 }
158 if (conf.max_msg_size && (data_size > conf.max_msg_size)) {
159 DEBUG(4) debugf("accept_message_stream(): "
160 "received %d bytes (conf.max_msg_size=%d)\n",
161 data_size, conf.max_msg_size);
162 return AERR_SIZE;
163 }
165 }
167 if (msg->data_list != NULL)
168 msg->data_list = g_list_reverse(msg->data_list);
169 else
170 /* make sure data list is not NULL: */
171 msg->data_list = g_list_append(NULL, g_strdup(""));
173 DEBUG(4) debugf("received %d lines of data (%d bytes)\n", line_cnt, data_size);
174 /* we get here after we succesfully received the mail data */
176 msg->data_size = data_size;
177 msg->received_time = time(NULL);
179 return AERR_OK;
180 }
182 accept_error
183 accept_message_prepare(message * msg, guint flags)
184 {
185 struct passwd *passwd = NULL;
186 GList *non_rcpt_list = NULL;
187 time_t rec_time = time(NULL);
189 DEBUG(5) debugf("accept_message_prepare()\n");
191 /* create unique message id */
192 msg->uid = g_malloc(14);
194 string_base62(msg->uid, rec_time, 6);
195 msg->uid[6] = '-';
196 string_base62(&(msg->uid[7]), getpid(), 3);
197 msg->uid[10] = '-';
198 string_base62(&(msg->uid[11]), msg->transfer_id, 2);
199 msg->uid[13] = 0;
201 /* if local, get password entry */
202 if (msg->received_host == NULL) {
203 passwd = g_memdup(getpwuid(geteuid()), sizeof(struct passwd));
204 msg->ident = g_strdup(passwd->pw_name);
205 }
207 /* set return path if local */
208 if (msg->return_path == NULL && msg->received_host == NULL) {
209 gchar *path = g_strdup_printf("<%s@%s>", passwd->pw_name, conf.host_name);
210 DEBUG(3) debugf("setting return_path for local accept: %s\n", path);
211 msg->return_path = create_address(path, TRUE);
212 g_free(path);
213 }
215 /* -t option (see comment above) */
216 if (flags & ACC_DEL_RCPTS) {
217 non_rcpt_list = msg->rcpt_list;
218 msg->rcpt_list = NULL;
219 }
221 /* scan headers */
222 {
223 gboolean has_id = FALSE;
224 gboolean has_date = FALSE;
225 gboolean has_sender = FALSE;
226 gboolean has_from = FALSE;
227 gboolean has_to_or_cc = FALSE;
228 GList *hdr_node, *hdr_node_next;
229 header *hdr;
231 for (hdr_node = g_list_first(msg->hdr_list);
232 hdr_node != NULL; hdr_node = hdr_node_next) {
233 hdr_node_next = g_list_next(hdr_node);
234 hdr = ((header *) (hdr_node->data));
235 DEBUG(5) debugf("scanning headers: %s", hdr->header);
236 switch (hdr->id) {
237 case HEAD_MESSAGE_ID:
238 has_id = TRUE;
239 break;
240 case HEAD_DATE:
241 has_date = TRUE;
242 break;
243 case HEAD_FROM:
244 has_from = TRUE;
245 break;
246 case HEAD_SENDER:
247 has_sender = TRUE;
248 break;
249 case HEAD_TO:
250 case HEAD_CC:
251 has_to_or_cc = TRUE;
252 /* fall through */
253 case HEAD_BCC:
254 if (flags & ACC_RCPT_FROM_HEAD) {
255 /* -t option (see comment above) */
256 DEBUG(5) debugf("hdr->value = %s\n", hdr->value);
257 if (hdr->value) {
258 msg->rcpt_list = addr_list_append_rfc822(msg->rcpt_list, hdr->value, conf.host_name);
259 }
260 }
261 if (hdr->id == HEAD_BCC) {
262 DEBUG(3) debugf("removing 'Bcc' header\n");
263 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
264 g_list_free_1(hdr_node);
265 destroy_header(hdr);
266 }
267 break;
268 case HEAD_ENVELOPE_TO:
269 if (flags & ACC_SAVE_ENVELOPE_TO) {
270 DEBUG(3) debugf("creating 'X-Orig-Envelope-To' header\n");
271 msg->hdr_list = g_list_prepend(msg->hdr_list, create_header(HEAD_UNKNOWN,
272 "X-Orig-Envelope-to: %s", hdr->value));
273 }
274 DEBUG(3) debugf("removing 'Envelope-To' header\n");
275 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
276 g_list_free_1(hdr_node);
277 destroy_header(hdr);
278 break;
279 case HEAD_RETURN_PATH:
280 if (flags & ACC_MAIL_FROM_HEAD) {
281 /* usually POP3 accept */
282 msg->return_path = create_address_qualified(hdr->value, TRUE, msg->received_host);
283 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
284 }
285 DEBUG(3) debugf("removing 'Return-Path' header\n");
286 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
287 g_list_free_1(hdr_node);
288 destroy_header(hdr);
289 break;
290 default:
291 break; /* make compiler happy */
292 }
293 }
295 if (msg->return_path == NULL) {
296 /* this can happen for pop3 accept only and if no Return-path: header was given */
297 GList *hdr_list;
298 header *hdr;
300 DEBUG(3) debugf("return_path == NULL\n");
302 hdr_list = find_header(msg->hdr_list, HEAD_SENDER, NULL);
303 if (!hdr_list)
304 hdr_list = find_header(msg->hdr_list, HEAD_FROM, NULL);
305 if (hdr_list) {
306 gchar *addr;
307 hdr = (header *) (g_list_first(hdr_list)->data);
309 DEBUG(5) debugf("hdr->value = '%s'\n", hdr->value);
311 addr = g_strdup(hdr->value);
312 g_strchomp(addr);
314 if ((msg->return_path = create_address_qualified(addr, FALSE, msg->received_host)) != NULL) {
315 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
316 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_UNKNOWN,
317 "X-Warning: return path set from %s address\n",
318 hdr->id == HEAD_SENDER ? "Sender:" : "From:"));
319 }
320 g_free(addr);
321 }
322 if (msg->return_path == NULL) { /* no Sender: or From: or create_address_qualified failed */
323 msg->return_path = create_address_qualified("postmaster", TRUE, conf.host_name);
324 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
325 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_UNKNOWN,
326 "X-Warning: real return path is unknown\n"));
327 }
328 }
330 if (flags & ACC_DEL_RCPTS) {
331 /* remove the recipients given on the command line from the ones given in headers
332 -t option (see comment above) */
333 GList *rcpt_node;
334 foreach(non_rcpt_list, rcpt_node) {
335 address *rcpt = (address *) (rcpt_node->data);
336 GList *node;
337 if ((node = g_list_find_custom(msg->rcpt_list, rcpt, _g_list_addr_isequal))) {
338 DEBUG(3) debugf("removing rcpt address %s\n", addr_string(node->data));
339 msg->rcpt_list = g_list_remove_link(msg->rcpt_list, node);
340 destroy_address((address *) (node->data));
341 g_list_free_1(node);
342 }
343 }
344 }
346 /* here we should have our recipients, fail if not: */
347 if (msg->rcpt_list == NULL) {
348 logwrite(LOG_WARNING, "no recipients found in message\n");
349 return AERR_NORCPT;
350 }
352 if (!(has_sender || has_from)) {
353 DEBUG(3) debugf("adding 'From' header\n");
354 msg->hdr_list = g_list_append(msg->hdr_list,
355 msg->full_sender_name
356 ?
357 create_header(HEAD_FROM, "From: \"%s\" <%s@%s>\n", msg->full_sender_name,
358 msg->return_path->local_part, msg->return_path->domain)
359 :
360 create_header(HEAD_FROM, "From: <%s@%s>\n",
361 msg->return_path->local_part, msg->return_path->domain)
362 );
363 }
364 if (!has_to_or_cc) {
365 DEBUG(3) debugf("no To: or Cc: header, hence adding `To: undisclosed recipients:;'\n");
366 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_TO, "To: undisclosed-recipients:;\n"));
367 }
368 if (!has_date) {
369 DEBUG(3) debugf("adding 'Date:' header\n");
370 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_DATE, "Date: %s\n", rec_timestamp()));
371 }
372 if (!has_id) {
373 DEBUG(3) debugf("adding 'Message-ID:' header\n");
374 msg->hdr_list = g_list_append(msg->hdr_list,
375 create_header(HEAD_MESSAGE_ID, "Message-ID: <%s@%s>\n", msg->uid, conf.host_name));
376 }
377 }
379 /* Received header: */
380 /* At this point because we have to know the rcpts for the 'for' part */
381 gchar *for_string = NULL;
382 header *hdr = NULL;
384 DEBUG(3) debugf("adding 'Received:' header\n");
386 if (g_list_length(msg->rcpt_list) == 1) {
387 address *addr = (address *) (g_list_first(msg->rcpt_list)->data);
388 for_string = g_strdup_printf(" for %s", addr_string(addr));
389 }
391 if (msg->received_host == NULL) {
392 /* received locally */
393 hdr = create_header(HEAD_RECEIVED, "Received: from %s by %s with %s (%s %s) id %s%s; %s\n",
394 passwd->pw_name, conf.host_name, prot_names[msg->received_prot],
395 PACKAGE, VERSION, msg->uid, for_string ? for_string : "", rec_timestamp());
396 } else {
397 /* received from remote */
398 #ifdef ENABLE_IDENT
399 DEBUG(5) debugf("adding 'Received:' header (5)\n");
400 hdr = create_header(HEAD_RECEIVED, "Received: from %s (ident=%s) by %s with %s (%s %s) id %s%s; %s\n",
401 msg->received_host, msg->ident ? msg->ident : "unknown", conf.host_name,
402 prot_names[msg->received_prot], PACKAGE, VERSION, msg->uid, for_string ? for_string : "",
403 rec_timestamp());
404 #else
405 hdr = create_header(HEAD_RECEIVED, "Received: from %s by %s with %s (%s %s) id %s%s; %s\n",
406 msg->received_host, conf.host_name, prot_names[msg->received_prot],
407 PACKAGE, VERSION, msg->uid, for_string ? for_string : "", rec_timestamp());
408 #endif
409 }
410 header_fold(hdr);
411 msg->hdr_list = g_list_prepend(msg->hdr_list, hdr);
413 if (for_string)
414 g_free(for_string);
416 /* write message to spool: */
417 /* accept is no longer responsible for this
418 if (!spool_write(msg, TRUE))
419 return AERR_NOSPOOL;
420 */
421 return AERR_OK;
422 }
424 accept_error
425 accept_message(FILE * in, message * msg, guint flags)
426 {
427 accept_error err;
429 err = accept_message_stream(in, msg, flags);
430 if (err == AERR_OK)
431 err = accept_message_prepare(msg, flags);
433 return err;
434 }