masqmail

annotate src/accept.c @ 323:29de6a1c4538

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